package model;

import java.io.Serializable;
import java.util.Observable;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicIntegerArray;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class World extends Observable implements Serializable {

	public int clock;
	// !!! a testar
	// http://www.javamex.com/tutorials/synchronizcation_concurrency_9_locks_j5b.shtml
	public final Lock lock = new ReentrantLock();
	public final Lock returnlock = new ReentrantLock();
	public volatile Boolean jogo_a_decorrer = true;

	public int getClock() {
		return clock;
	}

	public void setClock(int clock) {
		this.clock = clock;
	}

	public final int max_cobras = 1;
	public ConcurrentHashMap<Integer, AtomicIntegerArray> destinos_cobras = new ConcurrentHashMap<Integer, AtomicIntegerArray>();

	public AtomicIntegerArray getDestinos_cobras(int cobra_id) {
		return destinos_cobras.get(cobra_id);
	}

	public void setDestinos_cobras(int cobra_id, AtomicIntegerArray destino) {
		this.destinos_cobras.put(cobra_id, destino);
	}

	public void apagaDestinos_cobras(int cobra_id, AtomicIntegerArray destino) {
		this.destinos_cobras.remove(cobra_id, destino);
	}

	public AtomicIntegerArray criaDestinos(int origem, int destino) {

		AtomicIntegerArray destinos = new AtomicIntegerArray(2);
		destinos.set(0, destino % lado - origem % lado);
		destinos.set(1, destino / lado - origem / lado);

		return destinos;
	}

	public int speed;
	public int lado;
	public ConcurrentHashMap<Integer, Integer> food_counter = new ConcurrentHashMap<Integer, Integer>(
			max_cobras);

	public int getFood_counter(int cobra_id) {
		return food_counter.get(cobra_id);
	}

	public synchronized void increaseFood_counter(int cobra_id) {
		this.food_counter.put(cobra_id, 3);
	}

	public synchronized void decreaseFood_counter(int cobra_id) {
		this.food_counter.put(cobra_id, this.food_counter.get(cobra_id) - 1);
	}

	public ConcurrentHashMap<Integer, Boolean> controlo_humano_cobras = new ConcurrentHashMap<Integer, Boolean>(
			max_cobras);

	public ConcurrentHashMap<Integer, Boolean> cobras_com_salto = new ConcurrentHashMap<Integer, Boolean>(
			max_cobras);

	public ConcurrentHashMap<Integer, Integer> turbo_cobras = new ConcurrentHashMap<Integer, Integer>(
			max_cobras);

	public synchronized Boolean get_salto_da_cobra(int cobra_id) {
		return cobras_com_salto.get(cobra_id);
	}

	public void setCobras_com_salto(int cobra_id, boolean salto) {
		this.cobras_com_salto.put(cobra_id, salto);
	}

	public synchronized Boolean getControlo_humano_cobras(Integer cobra_id) {
		return this.controlo_humano_cobras.get(cobra_id);
	}

	public synchronized void setControlo_humano_cobras(Integer cobra_id,
			Boolean controlo_humano) {
		this.controlo_humano_cobras.put(cobra_id, controlo_humano);
		notifyObservers();
	}

	public int getCobrainCell(int celula) {

		for (int iii = 0; iii <= max_cobras; ++iii) {
			if (celulas_cobras.containsKey(iii)) {
				for (int ii = 0; ii != getCelulasCobra(iii).length(); ++ii) {
					for (int i = 0; i != getCelulasCobra(iii).length(); ++i) {

						if (getCelulasCobra(iii).get(ii) == celula)
							celula = iii;
					}
				}
			}
		}
		return celula;
	}

	public Boolean isCobrainCell(int celula) {

		Boolean esta_ca = false;

		for (int iii = 0; iii <= max_cobras; ++iii) {
			if (celulas_cobras.containsKey(iii)) {
				for (int ii = 0; ii != getCelulasCobra(iii).length(); ++ii) {
					for (int i = 0; i != getCelulasCobra(iii).length(); ++i) {

						if (getCelulasCobra(iii).get(ii) == celula)
							esta_ca = true;
					}
				}
			}
		}
		return esta_ca;

	}

	public ConcurrentHashMap<Integer, AtomicIntegerArray> celulas_cobras = new ConcurrentHashMap<Integer, AtomicIntegerArray>();

	public int getLado() {
		return lado;
	}

	public void setLado(int lado) {
		this.lado = lado;
	}

	public ConcurrentHashMap<Integer, Celula> tabuleiro;

	public Celula getCelula(Integer I) {
		/*
		 * lock.lock(); try {
		 * 
		 * } finally { lock.unlock(); }!!!
		 */
		return tabuleiro.get(I);
	}

	public int getTotalCobras() {
		return celulas_cobras.size();
	}

	// popula o mundo com bonus, obstaculos, comida e turbos
	public synchronized void populaMundo() {
		Random rand = new Random();
		for (int i = 0; i != lado * lado; ++i) {
			// !!! aumentar o numero diminui a complexidade do mundo
			switch (rand.nextInt(100)) {
			case 0:
				this.getCelula(i).setBlock();
				break;
			case 1:
				this.getCelula(i).setFood();
				break;
			case 2:
				this.getCelula(i).setTurbo();
				break;
			case 5:
				this.getCelula(i).setJump();
				break;
			case 7:
				this.getCelula(i).setFood();
				break;
			case 11:
				this.getCelula(i).setFood();
				break;
			case 12:
				this.getCelula(i).setBlock();
				break;
			case 13:
				this.getCelula(i).setBlock();
				break;
			}
			;

		}
		;
		notifyAll();
		notifyObservers();
		// setChanged();
		// notifyObservers();
		// notifyAll();
	}

	// devolve o índice da celula imediatamente abaixo da celula fornecida; se
	// for a parede, retorna o indice inalterado
	public Integer getDown(Integer celula) {
		if (celula / lado + 1 < lado)
			return celula + lado;
		return celula;
	}

	// devolve o índice da celula imediatamente acima da celula fornecida; se
	// for a parede, retorna o indice inalterado
	public Integer getUp(Integer celula) {

		if (celula / lado > 1)
			return celula - lado;
		return celula;
	}

	// devolve o índice da celula imediatamente à esquerda da celula fornecida;
	// se for a parede, retorna o indice inalterado
	public Integer getLeft(Integer celula) {
		if (celula % lado != 0)
			return celula - 1;
		return celula;
	}

	// devolve o índice da celula imediatamente à direita da celula fornecida;
	// se for a parede, retorna o indice inalterado
	public Integer getRight(Integer celula) {
		if (celula % lado - lado + 1 != 0)
			return celula + 1;
		return celula;
	}

	public synchronized void moveCobra(AtomicIntegerArray new_loc,
			Integer cobra_id) {

		celulas_cobras.put(cobra_id, new_loc);
		for (int i = 0; i != new_loc.length(); ++i) {
			tabuleiro.get(new_loc.get(i)).setNone();
			tabuleiro.get(new_loc.get(i)).busy.release();
			// release do semaforo justo permite a aquisição em fifo da celula.
		}
		setChanged();
		notifyObservers();

	}

	

	public synchronized void poeCobra(AtomicIntegerArray new_loc, int cobra_id) {

		celulas_cobras.put(cobra_id, new_loc);
		// reset ao bonus
		for (int i = 0; i != new_loc.length(); ++i)
			tabuleiro.get(new_loc.get(i)).setNone();
		setCobras_com_salto(cobra_id, false);
		setControlo_humano_cobras(cobra_id, false);
		food_counter.put(cobra_id, 0);
		turbo_cobras.put(cobra_id, 0);
		setChanged();
		notifyObservers();

		// System.out.println("Update called!");
	}

	private ExecutorService cobras;

	public void createSnakes() {

		for (int i = 0; i != max_cobras; ++i) {
			cobras = Executors.newFixedThreadPool(max_cobras);
			cobras.submit(new Cobra(this, 1 + i));
		}

	}

	public int getCobraTurbo(int cobra_id) {
		return turbo_cobras.get(cobra_id);
	}

	public void setCobraTurbo(int cobra_id) {
		// TODO Auto-generated method stub
		turbo_cobras.put(cobra_id, 3);
	}

	public AtomicIntegerArray getCelulasCobra(int cobra_id) {
		// !!!
		lock.lock();
		try {
			return (AtomicIntegerArray) celulas_cobras.get(cobra_id);
		} finally {
			lock.unlock();
		}

	}

	public synchronized void decCobraTurbo(int cobra_id) {
		turbo_cobras.put(cobra_id, turbo_cobras.get(cobra_id) - 3);

	}

	public World(int clock, int speed, int lado) {
		this.clock = clock;
		this.speed = speed;
		this.lado = lado;
		this.tabuleiro = new ConcurrentHashMap<Integer, Celula>();
		this.celulas_cobras = new ConcurrentHashMap<Integer, AtomicIntegerArray>();
		// popula o tabuleiro com a quantidade de células correspondentes ao
		// lado
		for (int i = 0; i != lado * lado; ++i) {
			this.tabuleiro.put(i, new Celula());

		}
		// tem de se criar cobras antes de popular o mundo! Mas ascobras vêm o
		// mundo vazio... ficam todas no mesmo sítio!

		populaMundo();
		createSnakes();

	}

}
